<?php
/**
 * Contains the Event_Handler class.
 * 
 * @package    event
 * @subpackage event
 * @author 	   Gerrit Jan van Ahee <gerrit.jan@yes2web.nl>
 * @copyright  2010-2011 Yes2web B.V.
 * @license    MIT License http://www.opensource.org/licenses/mit-license.php
 * @version    1.0.0
 * @link 	   www.yes2web.nl
 * @see        Event_Event
 */
/**
 * Event handlers listen to events from specific types of class. Upon construction,
 * the names of the classes the handler listens to should be provided, e.g.:
 * <code>
 * 	new Event_Handler('Class_A', 'Class_B', 'Class_C');
 * </code>
 * creates a new handler that can respond to each event triggered by classes Class_A,
 * Class_B or Class_C. Also, it will respond to any classes extending these. Besides
 * classnames, you can also create handlers specifying them to listen to interfaces;
 * this has them respond to each class that implements them (and their children):
 * <code>
 * 	new Event_Handler('Interface_A');
 * </code>
 * Multiple handlers can be registered to listen to the same classes. Each of them
 * will be notified when an event is triggered.
 * <code>
 * 	new Event_Handler('Class_A');
 * 	new Other_Handler('Class_A');
 * </code>
 * 
 * By default, no event originating from the classes and interfaces under
 * consideration is responded to. In order to act on an event, handlers should
 * implement methods (at least protected) based on the type of the event it wants to
 * respond to. The event-type is camel-cased and prefixed by "on":
 * Event_Event("before-delete") will trigger the method
 * "onBeforeDelete(Event_Event $oEvent)" in the handler.
 *
 * Events that have no corresponding methods will do nothing.
 *
 * In any Event_Handler->onEventType(Event_Event $oEvent) the triggering class
 * instance can be retrieved by calling $oEvent->getSource()
 * {@link Event_Event::getSource()}, as well as a number of other values.
 *
 * An event handler may raise the prevent-default flag by calling
 * {@link Event_Event::preventDefault()}. The triggering method can then check this
 * flag and decide whether or not to proceed {@link Event_Event}.
 *
 * @package    event
 * @subpackage event
 * @author 	   Gerrit Jan van Ahee <gerrit.jan@yes2web.nl>
 * @copyright  2010-2011 Yes2web B.V.
 * @license    MIT License http://www.opensource.org/licenses/mit-license.php
 * @version    1.0.0
 * @link 	   www.yes2web.nl
 * @see        Event_Event
 */
class Event_Handler
{
	/**
	 * @var Event_Handler[] list of hanlders
	 */
	private static $_aoHandler = array();

	/**
	 * Registers handelrs that will be informed by Events
	 *
	 * @param Event_Handler $oHandler the handler to register
	 *
	 * @return null
	 * @see   __construct()
	 * @see   singalEvent()
	 */
	protected static function register(Event_Handler $oHandler)
	{
		self::$_aoHandler[] = $oHandler;
	}

	/**
	 * Signal all handlers of an event
	 *
	 * @param Event_Event $oEvent the Event triggered
	 * 
	 * @return Mixed[] list of returned values from the event handlers
	 * @see    Event_Event::__construct()
	 */
	public static function signalEvent(Event_Event $oEvent)
	{
		$asOut = array();
		foreach (self::$_aoHandler as $oHandler) {
			$asOut[] = $oHandler->handleEvent($oEvent);
		}
		return $asOut;
	}

	/**
	 * @var String[] list of class names of classes this handler responds to
	 */
	protected $asHandlerFor = array();
	
	/**
	 * Construct a new Event_Handler to listen to classes with the provided names.
	 * 
	 * @param String $sDispatcherClassName class name of object to resopnd to, should
	 * be at least one, but may be more.
	 *
	 * @SuppressWarnings(PHPMD.LongVariable)
	 */
	public function __construct($sDispatcherClassName)
	{
		self::register($this);
		
		$asDispatcherClassName = func_get_args();
		foreach ($asDispatcherClassName as $sDispatcherClassName) {
			$this->asHandlerFor[] = $sDispatcherClassName;
		}
	}
	
	
	/**
	 * Get the handle of the event received, i.e. which method should handle Events
	 * of this type.
	 *
	 * @return String The handle to which a Handler should bind 
	 */
	protected function getEventHandle(Event_Event $oEvent)
	{
		return 'on'.implode(
			'', 
			array_map('ucfirst', explode('-', $oEvent->getType()))
		);
	}
	
	/**
	 * Check whether the event originates from an instance that is of the type of one
	 * of the dispatcher types that this handler has been bound to.
	 * 
	 * @param CAF_Event_Event $oEvent the event triggered
	 * 
	 * @return Mixed          whatever the handling method returns
	 */
	protected function handleEvent(Event_Event $oEvent)
	{
		$oSource = $oEvent->getSource();
		foreach ($this->asHandlerFor as $sDispatcherClassName) {
			// two cases:
			// 1. the source call was '->'
			// 2. the source call was '::'
			if ($oSource instanceof $sDispatcherClassName
				|| $oSource === $sDispatcherClassName			 
			) {
				// any handler handles at most one event from one dispatcher at a
				// time
				return $this->{$this->getEventHandle($oEvent)}($oEvent);
			}
		}
	}
	/**
	 * A handler may not need to handle ALL events dispatched by a source. This
	 * method will make sure that nothing crashes when an event is not handled, e.g.
	 * when new events are added to the source or events just not need to be handled
	 * in the first place. 
	 *  
	 * @param String $sHandle name of the method intended
	 * @param array  $amArgv  params provided to it
	 * 
	 * @return null  as this handler is apparentely not required
	 * @SuppressWarnings(PHPMD.LongVariable)
	 * @SuppressWarnings(PHPMD.UnusedFormalParameter)
	 */
	public function __call($sHandle, array $amArgv)
	{
		return null;// returns null in order to get 100% code coverage using phing :(
	}
}